package com.panghl.config.shiro;

import com.panghl.common.Constant;
import com.panghl.config.redis.RedisCache;
import com.panghl.config.shiro.filter.JwtToken;
import com.panghl.domain.ManagerInfo;
import com.panghl.service.ManagerInfoService;
import com.panghl.utils.JwtUtil;
import lombok.SneakyThrows;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @Author panghl
 * @Date 2022/2/13
 * @Description realm对象
 **/
@Component
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private RedisCache redisCache;

    /**
     * 大坑，必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof JwtToken;
    }

    /**
     * 认证 默认使用此方法进行用户名正确与否验证，错误抛出异常即可。
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("开始认证....");
        //获取用户的输入的账号.
        String token = (String) authenticationToken.getCredentials();
        //String userKey = Constant.PREFIX_SHIRO_ACCESS_TOKEN+ token;
        // 开始认证，要AccessToken认证通过，且Redis中存在RefreshToken，且两个Token时间戳一致
        //ManagerInfo managerInfo = (ManagerInfo)redisCache.getCacheObject(userKey);


            //交给UserRealm使用CredentialsMatcher进行密码匹配，如果觉得人家的不好可以自定义实现
            //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
            //        managerInfo.getUser(), //用户
            //        dbPwd, //密码
            //        ByteSource.Util.bytes(managerInfo.getCredentialsSalt()),//salt=username+salt
            //        getName()  //realm name
            //);

        //明文: 若存在，将此用户存放到登录认证info中，无需自己做密码对比，Shiro会为我们进行密码对比校验
        //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
        //        dbUsername, //用户名
        //        dbPwd, //密码
        //        getName()  //realm name
        //);
        return new SimpleAuthenticationInfo(token, token, "userRealm");
    }
    /**
     * 此方法调用hasRole,hasPermission的时候才会进行回调.
     * <p>
     * 权限信息.(授权):
     * 1、如果用户正常退出，缓存自动清空；
     * 2、如果用户非正常退出，缓存自动清空；
     * 3、如果我们修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。
     * （需要手动编程进行实现；放在service进行调用）
     * 在权限修改后调用realm中的方法，realm已经由spring管理，所以从spring中获取realm实例，调用clearCached方法；
     * :Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     *
     * @param principalCollection
     * @return
     */
    @SneakyThrows
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /*
         * 当没有使用缓存的时候，不断刷新页面的话，这个代码会不断执行，
         * 当其实没有必要每次都重新设置权限信息，所以我们需要放到缓存中进行管理；
         * 当放到缓存中时，这样的话，doGetAuthorizationInfo就只会执行一次了，
         * 缓存过期之后会再次执行。
         */
        System.out.println("开始授权....");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String token = principalCollection.getPrimaryPrincipal().toString();
        String userKey = Constant.PREFIX_SHIRO_ACCESS_TOKEN+  JwtUtil.parseJWT(token).getSubject().toString();
        System.out.println("userKey="+userKey);
        ManagerInfo managerInfo = (ManagerInfo)redisCache.getCacheObject(userKey);

        if (Objects.isNull(managerInfo)){
            throw new RuntimeException("token 失效");
        }

        //设置相应角色的权限信息
        for (String role : managerInfo.getRoles()) {
            authorizationInfo.addRole(role);
        }
        for (String permission : managerInfo.getPermissions()) {
            authorizationInfo.addStringPermission(permission);
        }
        return authorizationInfo;
    }
}
